package sm.dbdict;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sm.dam.api.DDLAdaptor;
import sm.dam.api.DDLAdaptorFactory;
import sm.dbdict.change.DBDictChange;
import sm.dbdict.change.SqlColumnChange;
import sm.dbdict.change.SqlTableChange;
import sm.dbdict.def.DBDict;
import sm.dbdict.def.Field;
import sm.dbdict.def.Key;
import sm.dbdict.def.SqlFieldOptions;
import sm.dbdict.def.SqlTables;
import sm.def.KeyType;
import sm.def.Type;
import sm.util.Util;

public class DBDictChangeUtil {
	private static Logger logger = LoggerFactory
			.getLogger(DBDictChangeUtil.class);

	private static void ignoreChange(Field upgradeField, DBDict customerDBDict) {
		String fieldPath = DBDictUtil.getFieldPath(upgradeField);
		Field customerField = DBDictUtil.getField(fieldPath,
				customerDBDict.getField());
		if (customerField != null) {
			upgradeField.setSql_field_options((SqlFieldOptions) customerField
					.getSql_field_options().clone());
		}
	}

	private static int ignoreChangeWithSubFields(Field upgradeField,
			DBDict upgradeDBDict, DBDict customerDBDict) {
		ignoreChange(upgradeField, customerDBDict);
		List<Field> subFields = DBDictUtil.getSubFields(
				upgradeDBDict.getField(), upgradeField);
		for (Field subField : subFields) {
			ignoreChange(subField, customerDBDict);
		}
		return subFields.size();
	}

	private static boolean ignoreKeyWithNewFields(Key key, DBDict dbdict) {
		boolean ignoreKey = false;
		for (String upgradeKeyPath : key.getName()) {
			Field customerField = DBDictUtil.getKeyField(upgradeKeyPath,
					dbdict.getField());
			if (customerField == null) {
				ignoreKey = true;
				break;
			}
		}
		return ignoreKey;
	}

	public static List<Key> getNewKeys(DBDict customerDBDict,
			DBDict upgradeDBDict) {
		List<Key> newUniqueKeys = new ArrayList<Key>();
		List<Key> newNotNullKeys = new ArrayList<Key>();
		for (Key upgradeKey : upgradeDBDict.getKey()) {
			int flags = upgradeKey.getFlags();
			if (KeyType.isUniqueKey(flags)) {
				Key customerKey = getSameKey(customerDBDict.getKey(),
						upgradeKey, -1, true);
				if (customerKey == null) {
					boolean ignoreKey = ignoreKeyWithNewFields(upgradeKey,
							customerDBDict);
					if (!ignoreKey) {
						newUniqueKeys.add(upgradeKey);
					}
				}
			} else if (KeyType.isNotNullKey(flags)) {
				Key customerKey = getSameKey(customerDBDict.getKey(),
						upgradeKey);
				if (customerKey == null) {
					boolean ignoreKey = ignoreKeyWithNewFields(upgradeKey,
							customerDBDict);
					if (!ignoreKey) {
						newNotNullKeys.add(upgradeKey);
					}
				}
			}
		}

		for (ListIterator<Key> it = newUniqueKeys.listIterator(); it.hasNext();) {
			Key uniqueKey = it.next();
			Key sameKey = getSameKey(newUniqueKeys, uniqueKey, -2, true);
			if (sameKey != null) {
				// drop smaller keys
				it.remove();
			}
		}

		for (ListIterator<Key> it = newNotNullKeys.listIterator(); it.hasNext();) {
			Key notNullKey = it.next();
			Key sameKey = getSameKey(newNotNullKeys, notNullKey, 2, false);
			if (sameKey != null) {
				// drop bigger keys
				it.remove();
			}
		}

		List<Key> newKeys = new ArrayList<Key>();
		// fix not null key before unique key
		newKeys.addAll(newNotNullKeys);
		newKeys.addAll(newUniqueKeys);
		return newKeys;
	}

	public static List<Field> getRCFieldChange(DBDict customerDBDict,
			DBDict upgradeDBDict) {
		List<Field> rcFieldsChange = new ArrayList<Field>();
		// validate field types
		List<Field> upgradeFields = upgradeDBDict.getField();
		for (int i = 0; i < upgradeFields.size(); i++) {
			Field upgradeField = upgradeFields.get(i);
			SqlFieldOptions upgradeSqlFieldOptions = upgradeField
					.getSql_field_options();
			String fieldPath = DBDictUtil.getFieldPath(upgradeField);
			Field customerField = DBDictUtil.getField(fieldPath,
					customerDBDict.getField());
			if (customerField != null) {
				SqlFieldOptions customerSqlFieldOptions = customerField
						.getSql_field_options();
				boolean customerRC = customerSqlFieldOptions.getSql_rc_format() != null ? customerSqlFieldOptions
						.getSql_rc_format() : false;
				boolean upgradeRC = upgradeSqlFieldOptions.getSql_rc_format() != null ? upgradeSqlFieldOptions
						.getSql_rc_format() : false;

				if (DBDictUtil.isDBColumn(upgradeField) && !upgradeRC) {
					if (customerRC || !DBDictUtil.isDBColumn(customerField)) {
						rcFieldsChange.add(customerField);
					}
				}

			}
		}
		return rcFieldsChange;
	}

	public static boolean checkDBDictChange(DBDict customerDBDict,
			DBDict upgradeDBDict, DBDictChange dbdictChange,
			DBDictChangePolicy policy) {
		boolean hasChange = false;
		String fileName = upgradeDBDict.getName();
		// validate field types
		List<Field> upgradeFields = upgradeDBDict.getField();
		List<String> newFields = new ArrayList<String>();
		for (int i = 0; i < upgradeFields.size(); i++) {
			Field upgradeField = upgradeFields.get(i);
			SqlFieldOptions upgradeSqlFieldOptions = upgradeField
					.getSql_field_options();
			int upgradeFieldType = upgradeField.getType();

			String fieldPath = DBDictUtil.getFieldPath(upgradeField);
			Field customerField = DBDictUtil.getField(fieldPath,
					customerDBDict.getField());
			if (customerField != null) {
				int customerFieldType = customerField.getType();
				SqlFieldOptions customerSqlFieldOptions = customerField
						.getSql_field_options();
				boolean customerRC = customerSqlFieldOptions.getSql_rc_format() != null ? customerSqlFieldOptions
						.getSql_rc_format() : false;
				boolean upgradeRC = upgradeSqlFieldOptions.getSql_rc_format() != null ? upgradeSqlFieldOptions
						.getSql_rc_format() : false;
				String customerAlias = customerSqlFieldOptions
						.getSql_table_alias();
				String upgradeAlias = upgradeSqlFieldOptions
						.getSql_table_alias();
				String customerSqlType = customerSqlFieldOptions
						.getSql_data_type();
				String upgradeSqlType = upgradeSqlFieldOptions
						.getSql_data_type();
				if (policy != null
						&& policy.needIgnoreFieldChange(fileName, fieldPath)) {
					logger.warn("ignore change -- dbdict:" + fileName
							+ ", field: " + fieldPath);
					if (Type.isPrimitiveType(upgradeField.getType())) {
						ignoreChange(upgradeField, customerDBDict);
					} else {
						int subFieldsSize = ignoreChangeWithSubFields(
								upgradeField, upgradeDBDict, customerDBDict);
						i += subFieldsSize;
					}
				} else {
					if (upgradeFieldType != customerFieldType) {
						logger.info("dbdict:" + fileName + ", field:"
								+ DBDictUtil.getFieldPath(upgradeField)
								+ ", field type is "
								+ Type.getType(customerFieldType).getName()
								+ " -- expected to be:"
								+ Type.getType(upgradeFieldType).getName());
						hasChange = true;
					} else if (customerRC != upgradeRC) {
						logger.info("dbdict:" + fileName + ", field:"
								+ DBDictUtil.getFieldPath(upgradeField)
								+ ", field RC is " + customerRC
								+ " -- expected to be:" + upgradeRC);
						hasChange = true;
					} else if (!Util.equals(customerAlias, upgradeAlias)) {
						hasChange = true;
						logger.info("dbdict:" + fileName + ", field:"
								+ DBDictUtil.getFieldPath(upgradeField)
								+ ", field alias is " + customerAlias
								+ " -- expected to be:" + upgradeAlias);
					} else if (upgradeSqlType != null
							&& customerSqlType != null) {
						String tableAlias = customerSqlFieldOptions
								.getSql_table_alias();
						SqlTables sqlTable = DBDictUtil.getTableByAlias(
								customerDBDict.getSql_tables(), tableAlias);
						DDLAdaptor ddlAdaptor = DDLAdaptorFactory
								.getDDLAdaptor(sqlTable.getSql_db_type());
						if (!ddlAdaptor.isSameType(upgradeSqlType,
								customerSqlType)) {
							hasChange = true;
							logger.info("dbdict:" + fileName + ", field:"
									+ DBDictUtil.getFieldPath(upgradeField)
									+ ", SQL type is " + customerSqlType
									+ " -- expected to be:" + upgradeSqlType);
						} else if (ddlAdaptor.isBigger(upgradeSqlType,
								customerSqlType)) {
							if (logger.isDebugEnabled()) {
								logger.debug("Increasing field length for "
										+ fieldPath + " in " + fileName
										+ " dbdict from " + customerSqlType
										+ " to " + upgradeSqlType);
							}
							SqlColumnChange columnChange = new SqlColumnChange();
							columnChange.setColumnName(customerSqlFieldOptions
									.getSql_column_name());
							columnChange.setDataType(upgradeSqlType);
							addColumnChange(dbdictChange,
									sqlTable.getSql_table_name(), columnChange);
						}

					}
				}
			} else { // new field
				newFields.add(fieldPath);
				if (DBDictUtil.isDBField(upgradeField)) {
					String tableAlias = upgradeSqlFieldOptions
							.getSql_table_alias();
					SqlTables upgradeTable = DBDictUtil.getTableByAlias(
							upgradeDBDict.getSql_tables(), tableAlias);
					SqlTables customerTable = getMatchedTable(upgradeTable,
							upgradeDBDict, customerDBDict);
					if (customerTable != null) {
						SqlColumnChange columnChange = new SqlColumnChange();
						columnChange.setColumnName(upgradeSqlFieldOptions
								.getSql_column_name());
						columnChange.setDataType(upgradeSqlFieldOptions
								.getSql_data_type());
						addNewColumn(dbdictChange,
								customerTable.getSql_table_name(), columnChange);
					} else {
						dbdictChange.getNewTables().add(upgradeTable);
					}

				}
			}
		}

		List<Key> upgradeKeys = upgradeDBDict.getKey();
		for (ListIterator<Key> it = upgradeKeys.listIterator(); it.hasNext();) {
			Key upgradeKey = it.next();
			int upgradeKeyFlags = upgradeKey.getFlags();
			if (KeyType.isUniqueKey(upgradeKeyFlags)) {
				String upgradeKeyStr = DBDictUtil.getKeyString(upgradeKey);
				if (policy != null
						&& policy.needIgnoreUniqueKey(fileName, upgradeKeyStr)) {
					logger.warn("ignore unique key -- " + upgradeKeyStr);
					it.remove();
				}
			}
		}

		String upgradeUniqueKeyStr = DBDictUtil.getUniqueKeyStr(upgradeDBDict);
		String customerUniqueKeyStr = DBDictUtil
				.getUniqueKeyStr(customerDBDict);
		if (!upgradeUniqueKeyStr.equals(customerUniqueKeyStr)) {
			logger.info("dbdict:" + fileName + ", Unique Key is "
					+ customerUniqueKeyStr + " -- expected to be:"
					+ upgradeUniqueKeyStr);
			hasChange = true;
		}

		if (!hasChange && logger.isDebugEnabled() && newFields.size() > 0) {
			StringBuilder builder = new StringBuilder();
			builder.append("Fields added to dbdict ").append(fileName)
					.append(": ");
			for (int i = 0; i < newFields.size(); i++) {
				String newFieldName = newFields.get(i);
				builder.append(newFieldName);
				if (i < newFields.size() - 1) {
					builder.append(", ");
				}
			}
			logger.debug(builder.toString());
		}
		return hasChange;
	}

	private static SqlTableChange getOrCreateTableChange(DBDictChange change,
			String tableName) {
		Map<String, SqlTableChange> tableChanges = change.getTableChanges();
		SqlTableChange tableChange = tableChanges.get(tableName);
		if (tableChange == null) {
			tableChange = new SqlTableChange();
			tableChanges.put(tableName, tableChange);
		}
		return tableChange;
	}

	private static void addColumnChange(DBDictChange change, String tableName,
			SqlColumnChange columnChange) {
		SqlTableChange tableChange = getOrCreateTableChange(change, tableName);
		tableChange.getColumnChanges().add(columnChange);
	}

	private static void addNewColumn(DBDictChange change, String tableName,
			SqlColumnChange columnChange) {
		SqlTableChange tableChange = getOrCreateTableChange(change, tableName);
		tableChange.getNewColumns().add(columnChange);
	}

	/**
	 * get the same key
	 * 
	 * @param keys
	 * @param key
	 * @param type
	 * @return
	 */
	public static Key getSameKey(List<Key> keys, Key key) {
		String keyStr = DBDictUtil.getKeyString(key);
		Key sameKey = null;
		for (Key k : keys) {
			if (key.getFlags() == k.getFlags()) {
				String kStr = DBDictUtil.getKeyString(k);
				if (keyStr.equals(kStr)) {
					sameKey = k;
					break;
				}
			}
		}
		return sameKey;
	}

	/**
	 * get the same key
	 * 
	 * @param keys
	 * @param key
	 * @param compareFactor
	 *            0:equal only, 1:bigger or equal, 2:bigger only, -1:smaller or
	 *            equal, -2:smaller only, others:equal or bigger or smaller
	 * @param unique
	 * @return
	 */
	public static Key getSameKey(List<Key> keys, Key key, int compareFactor,
			boolean unique) {
		Key sameKey = null;
		for (Key k : keys) {
			boolean skip = false;
			if (unique && !KeyType.isUniqueKey(k.getFlags())) {
				skip = true;
			}

			if (!skip) {
				boolean bigger = k.getName().containsAll(key.getName());
				boolean smaller = key.getName().containsAll(k.getName());
				if (compareFactor == 2) {
					if (bigger && !smaller) {
						sameKey = k;
						break;
					}
				} else if (compareFactor == 1) {
					if (bigger) {
						if (smaller) {
							sameKey = k;
							break;
						} else {
							sameKey = k;
						}
					}
				} else if (compareFactor == 0) {
					if (bigger && smaller) {
						sameKey = k;
						break;
					}
				} else if (compareFactor == -1) {
					if (smaller) {
						if (bigger) {
							sameKey = k;
							break;
						} else {
							sameKey = k;
						}
					}
				} else if (compareFactor == -2) {
					if (smaller && !bigger) {
						sameKey = k;
						break;
					}
				} else {
					if (smaller && bigger) {
						sameKey = k;
						break;
					} else if (smaller || bigger) {
						sameKey = k;
					}
				}
			}
		}

		return sameKey;
	}

	/**
	 * get the matched sql table
	 * 
	 * @param sqlTable
	 * @param upgradeDBDict
	 * @param customerDBDict
	 * @return
	 */
	public static SqlTables getMatchedTable(SqlTables upgradeTable,
			DBDict upgradeDBDict, DBDict customerDBDict) {
		String alias = upgradeTable.getSql_table_alias();
		List<Field> fields = DBDictUtil.getTableFields(
				upgradeDBDict.getField(), alias);
		String matchedTableAlias = null;
		for (Field field : fields) {
			if (DBDictUtil.isDBField(field)) {
				String path = DBDictUtil.getFieldPath(field);
				Field matchedField = DBDictUtil.getField(path,
						customerDBDict.getField());
				if (matchedField != null && DBDictUtil.isDBField(matchedField)) {
					matchedTableAlias = matchedField.getSql_field_options()
							.getSql_table_alias();
					break;
				}
			}
		}

		if (matchedTableAlias != null) {
			boolean isTableArray = DBDictUtil.isArrayTable(fields);
			boolean isMatchedTableArray = DBDictUtil.isArrayTable(
					customerDBDict, matchedTableAlias);
			if (isTableArray == isMatchedTableArray) {
				return DBDictUtil.getTableByAlias(
						customerDBDict.getSql_tables(), matchedTableAlias);
			}
		}
		return null;
	}

	/**
	 * get the matched table related to key
	 * 
	 * @param customerKey
	 * @param upgradeDBDict
	 * @return
	 */
	public static SqlTables getKeyRelatedTable(Key customerKey,
			DBDict upgradeDBDict) {
		SqlTables sqlTable = null;
		String alias = null;
		for (String name : customerKey.getName()) {
			Field field = DBDictUtil
					.getKeyField(name, upgradeDBDict.getField());
			if (field != null && DBDictUtil.isDBField(field)) {
				String tableAlias = field.getSql_field_options()
						.getSql_table_alias();
				if (alias != null && !alias.equals(tableAlias)) {
					sqlTable = null;
					break;
				} else if (sqlTable == null) {
					sqlTable = DBDictUtil.getTableByAlias(
							upgradeDBDict.getSql_tables(), tableAlias);
				}
			} else {
				sqlTable = null;
				break;
			}
		}
		return sqlTable;
	}
}
